﻿using IOP.Models.Tree.BinaryTree;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using IOP.Models.Data.Attributes;
using System.ComponentModel;
using System.Reflection;
using IOP.Extension;

namespace IOP.Models.Data
{
    /// <summary>
    /// 数据监视者
    /// </summary>
    /// <typeparam name="TEditModel"></typeparam>
    public class DataMonitor<TEditModel> : IDisposable
        where TEditModel : class, IDataValidation, new()
    {
        /// <summary>
        /// 模型是否发生了变更
        /// </summary>
        public bool IsChanged { get => ChangedProperty.Count > 0; }
        /// <summary>
        /// 源模型
        /// </summary>
        private TEditModel SourceModel { get; set; }
        /// <summary>
        /// 镜像模型
        /// </summary>
        private TEditModel MirrorModel { get; set; } = new TEditModel();
        /// <summary>
        /// 模型状态
        /// </summary>
        private readonly DataState EditModelState;
        /// <summary>
        /// 是否发生回退
        /// </summary>
        private bool IsRetroversion { get; set; } = false;

        /// <summary>
        /// 修改历史
        /// </summary>
        private readonly Stack<Node<string, DataStamp>> EditHistory = new Stack<Node<string, DataStamp>>();
        /// <summary>
        /// 发生变化的属性
        /// </summary>
        private readonly RedBlackTree<string, DataStamp> ChangedProperty = new RedBlackTree<string, DataStamp>();
        /// <summary>
        /// 属性字典
        /// </summary>
        private readonly Dictionary<string, PropertyInfo> PropertyDictionary = new Dictionary<string, PropertyInfo>();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="editModel"></param>
        /// <param name="state"></param>
        public DataMonitor(TEditModel editModel, DataState state)
        {
            SourceModel = editModel ?? throw new ArgumentNullException(nameof(editModel));
            EditModelState = state;
            Init();
        }

        /// <summary>
        /// 获取修改历史纪录列表
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Node<string, DataStamp>> GetEditHistory()
        {
            foreach(var item in EditHistory)
            {
                yield return item;
            }
        }

        /// <summary>
        /// 获取发生变更的属性以及其值
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Node<string, object>> GetChangedProperty()
        {
            var properties = ChangedProperty.InOrder();
            foreach(var item in properties)
            {
                var node = new Node<string, object>(item.Key, item.Value.EndValue);
                yield return node;
            }
        }

        /// <summary>
        /// 回退
        /// </summary>
        public void Retroversion()
        {
            if(EditHistory.Count > 0)
            {
                IsRetroversion = true;
                var node = EditHistory.Pop();
                var originValue = MirrorModel.GetPropertyValue(node.Key);
                if(originValue == null)
                {
                    if (node.Value.StartValue != null)
                    {
                        DataStamp stamp = new DataStamp(node.Key, node.Value.EndValue, node.Value.StartValue);
                        ChangedProperty.Put(node.Key, stamp);
                    }
                    else ChangedProperty.Delete(node.Key);
                }
                else
                {
                    if (!node.Value.StartValue.Equals(originValue))
                    {
                        DataStamp stamp = new DataStamp(node.Key, node.Value.EndValue, node.Value.StartValue);
                        ChangedProperty.Put(node.Key, stamp);
                    }
                    else ChangedProperty.Delete(node.Key);
                }
                SourceModel.SetPropertyValue(node.Key, node.Value.StartValue);
            }
        }

        /// <summary>
        /// 销毁资源
        /// </summary>
        public void Dispose()
        {
            EditHistory.Clear();
            ChangedProperty.Clear();
            PropertyDictionary.Clear();
            SourceModel.DataStampChanged -= SourceModel_DataStampChanged;
            SourceModel = null;
            MirrorModel = null;
        }

        /// <summary>
        /// 初始化
        /// </summary>
        private void Init()
        {
            CopyDataToMirror();
            SourceModel.DataStampChanged += SourceModel_DataStampChanged;
            var type = typeof(TEditModel);
            foreach(var property in type.GetProperties())
            {
                if (!property.GetCustomAttributes(true).Any(x => x is IgnorePropertyAttribute))
                {
                    PropertyDictionary.Add(property.Name, property);
                }
            }
        }

        /// <summary>
        /// 源属性数据戳发生变更时
        /// </summary>
        /// <param name="e"></param>
        private void SourceModel_DataStampChanged(DataStamp e)
        {
            if (IsRetroversion)
            {
                IsRetroversion = false;
                return;
            }
            if (PropertyDictionary.Keys.Contains(e.PropertyName))
            {
                var originValue = MirrorModel.GetPropertyValue(e.PropertyName);
                if (originValue == e.EndValue)
                {
                    if (ChangedProperty.Contains(e.PropertyName, out DataStamp oldvalue))
                        ChangedProperty.Delete(e.PropertyName);
                }
                else ChangedProperty.Put(e.PropertyName, e);
                Node<string, DataStamp> node = new Node<string, DataStamp>(e.PropertyName, e);
                EditHistory.Push(node);
            }
        }

        /// <summary>
        /// 复制一份数据至镜像
        /// </summary>
        private void CopyDataToMirror()
        {
            var type = typeof(TEditModel);
            ParameterExpression sourceExpression = Expression.Parameter(type, "x");
            ParameterExpression targetExpression = Expression.Parameter(type, "y");
            List<Expression> expressions = new List<Expression>();
            foreach (var property in type.GetProperties())
            {
                if (!property.GetCustomAttributes(true).Any(x => x is IgnorePropertyAttribute))
                {
                    MemberExpression originProperty = Expression.Property(sourceExpression, property);
                    MemberExpression sourceProperty = Expression.Property(targetExpression, property);
                    BinaryExpression assign = Expression.Assign(sourceProperty, originProperty);
                    expressions.Add(assign);
                }
            }
            BlockExpression block = Expression.Block(expressions);
            Expression<Action<TEditModel, TEditModel>> lambda = Expression.Lambda<Action<TEditModel, TEditModel>>(block, sourceExpression, targetExpression);
            var func = lambda.Compile();
            func(SourceModel, MirrorModel);
        }
    }
}
